import { FieldType } from '@boundaryml/baml/native'
import { TypeBuilder as _TypeBuilder, EnumBuilder, EnumViewer, ClassBuilder, ClassViewer } from '@boundaryml/baml/type_builder'
import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals"

export { FieldType, EnumBuilder, ClassBuilder }

export default class TypeBuilder {
    private tb: _TypeBuilder;
    {% for cls in classes %}
    {{cls.name}}: {% if cls.dynamic %}ClassBuilder{% else %}ClassViewer{% endif %}<'{{cls.name}}'
    {%- for field in cls.fields %}{% if loop.first %}, {%endif%}"{{field.name}}"{% if !loop.last %} | {% endif %}{% endfor -%}
    >;
    {% endfor %}
    {% for e in enums %}
    {{e.name}}: {% if e.dynamic %}EnumBuilder{% else %}EnumViewer{% endif %}<'{{e.name}}'{%- for (value, _) in e.values %}{% if loop.first %}, {%endif%}"{{value}}"{% if !loop.last %} | {% endif %}{% endfor -%}>;
    {% endfor %}

    constructor() {
        this.tb = new _TypeBuilder({
          classes: new Set([
            {% for cls in classes %}"{{cls.name}}",{% endfor %}
          ]),
          enums: new Set([
            {% for e in enums %}"{{e.name}}",{% endfor %}
          ]),
          runtime: DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME
        });
        {% for cls in classes %}
        this.{{cls.name}} = this.tb.{% if cls.dynamic %}classBuilder{% else %}classViewer{% endif %}("{{cls.name}}", [
          {% for field in cls.fields %}"{{field.name}}",{% endfor %}
        ]);
        {% endfor %}
        {% for e in enums %}
        this.{{e.name}} = this.tb.{% if e.dynamic %}enumBuilder{% else %}enumViewer{% endif %}("{{e.name}}", [
          {% for (value, _) in e.values %}"{{value}}",{% endfor %}
        ]);
        {% endfor %}
    }

    reset(): void {
        this.tb.reset();
        // TODO: This should happen in Rust. Problem is, when we construct the
        // typebuilder we instantiate class builders once and it seems to make
        // a JS copy, bypassing the Rust side? In Python however, every time we
        // access a class builder with @property, we get a new instance that
        // wraps over the Rust type builder, so we only need to call tb.reset().
        // In JS it's not possible unless we refactor the way class builders are
        // accessed.
        {% for cls in classes %}{% if cls.dynamic %} this.{{cls.name}}.reset(); {% endif %}{% endfor %}
    }

    __tb() {
      return this.tb._tb();
    }

    string(): FieldType {
        return this.tb.string()
    }

    literalString(value: string): FieldType {
        return this.tb.literalString(value)
    }

    literalInt(value: number): FieldType {
        return this.tb.literalInt(value)
    }

    literalBool(value: boolean): FieldType {
        return this.tb.literalBool(value)
    }

    int(): FieldType {
        return this.tb.int()
    }

    float(): FieldType {
        return this.tb.float()
    }

    bool(): FieldType {
        return this.tb.bool()
    }

    list(type: FieldType): FieldType {
        return this.tb.list(type)
    }

    null(): FieldType {
        return this.tb.null()
    }

    map(key: FieldType, value: FieldType): FieldType {
        return this.tb.map(key, value)
    }

    union(types: FieldType[]): FieldType {
        return this.tb.union(types)
    }

    addClass<Name extends string>(name: Name): ClassBuilder<Name> {
        return this.tb.addClass(name);
    }

    addEnum<Name extends string>(name: Name): EnumBuilder<Name> {
        return this.tb.addEnum(name);
    }

    addBaml(baml: string): void {
        this.tb.addBaml(baml);
    }
}
